package textdiff {
	
	/**
	 * Compares two text files or arrays of strings and generates a report of edit
	 * commands that would transform Old to New.
	 */
	public class TextDiff {
	    public static const OLD: int = 0;
	    public static const NEW: int = 1;
	
	    private var symbols: SymbolCollection;
	    private var oldFileInfo: FileInfo;
	    private var newFileInfo: FileInfo;
	
	    public function TextDiff() {
	    }
	
	    /** Compare two string arrays */
	    public function compare(oldLines: Array, newLines: Array, targetReport:Report): void {
	        createFileInfo( oldLines, newLines );
	        createSymbols();
	        createLineInfo();
	        stretchMatches( oldFileInfo );
	        if (targetReport == null) targetReport = new Report();
	        targetReport.createReport( oldFileInfo, newFileInfo );
	    }
	
	    private function createFileInfo(oldLines: Array, newLines: Array): void {
	        oldFileInfo = new FileInfo( oldLines );
	        newFileInfo = new FileInfo( newLines );
	    }
	
	    /** Create a symbol for each unique string */
	    private function createSymbols(): void {
	        symbols = new SymbolCollection();
	        _createSymbols( oldFileInfo, OLD );
	        _createSymbols( newFileInfo, NEW );
	    }
	
	    private function _createSymbols(fileInfo: FileInfo, fileIx: int): void{ 
	        for (var line:int = 0; line < fileInfo.length; line++)
	            symbols.registerSymbol( fileIx, fileInfo.lines[line], line );
	    }
	
	    /** Initial line status is symbol status. Cross link any UniqueMatch lines */
	    private function createLineInfo(): void {
	        _createLineInfo( oldFileInfo );
	        _createLineInfo( newFileInfo );
	    }
	
	    private function _createLineInfo(fileInfo: FileInfo): void {
	        for (var line:int = 0; line < fileInfo.length; line++)
	        {
	            var lineInfo: LineInfo = new LineInfo();
	            fileInfo.lineInfo[line] = lineInfo;
	            var symbol: Symbol  = symbols.getSymbolFor( fileInfo.lines[line] );
	            lineInfo.lineStatus = symbol.state;
	            if (lineInfo.isMatch())
	            {
	                lineInfo.oldLineNum = symbol.getLineNum( OLD );
	                lineInfo.newLineNum = symbol.getLineNum( NEW );
	            }
	        }
	    }
	
	    /** Stretch each unique-match in the FileInfo. */
	    private function stretchMatches(fileInfo: FileInfo): void {
	        var FORWARD: int  = 1;
	        var BACKWARD: int  = -1;
	        var lBlockNum: int  = 0;
	        for (var line:int = 0; fileInfo.isValidLineNum( line ); line++)
	        {
	            var lineInfo: LineInfo = fileInfo.lineInfo[line];
	            if ((lineInfo.isMatch()) && (lineInfo.blockNum == 0))
	            {
	                lBlockNum++;
	                stretchOneMatch( lBlockNum, lineInfo.oldLineNum, lineInfo.newLineNum, FORWARD );
	                stretchOneMatch( lBlockNum, lineInfo.oldLineNum, lineInfo.newLineNum, BACKWARD );
	            }
	        }
	    }
	
	    /**
	     * Find more matching lines before or after a unique match and mark them as
	     * unique match, too. If unique match lines are separated by matching but
	     * non-unique lines this will merge them all into one block.
	     */
	    private function stretchOneMatch(blockNum:int, oldLineNum:int, newLineNum:int, whichWay:int): void {
	        var lOldLineNum:int = oldLineNum;
	        var lNewLineNum:int = newLineNum;
	        while (true)
	        {
	            oldFileInfo.setBlockNumber( lOldLineNum, blockNum );
	            newFileInfo.setBlockNumber( lNewLineNum, blockNum );
	            oldFileInfo.lineInfo[lOldLineNum].newLineNum = lNewLineNum;
	            newFileInfo.lineInfo[lNewLineNum].oldLineNum = lOldLineNum;
	
	            lOldLineNum += whichWay;
	            lNewLineNum += whichWay;
	            if ( !(oldFileInfo.isValidLineNum(lOldLineNum) && newFileInfo.isValidLineNum(lNewLineNum) 
	            		&& oldFileInfo.lines[lOldLineNum] == newFileInfo.lines[lNewLineNum]) )
	                break;
	        }
	    }
	}
}